<?php

namespace App\Admin;

use App\Admin\Exception\Handle;
use App\Admin\Form\Builder;
use App\Admin\Form\Field;
use App\Admin\Form\Tab;
use App\Admin\Traits\CanValidate;
use App\Models\AdminUser;
use App\Models\File as FileManager;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

/**
 * Class Form.
 *
 * @method Field\Text           text($column, $label = '')
 * @method Field\Checkbox       checkbox($column, $label = '')
 * @method Field\Radio          radio($column, $label = '')
 * @method Field\Select         select($column, $label = '')
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
 * @method Field\Textarea       textarea($column, $label = '')
 * @method Field\Hidden         hidden($column, $label = '')
 * @method Field\Id             id($column, $label = '')
 * @method Field\Ip             ip($column, $label = '')
 * @method Field\Color          color($column, $label = '')
 * @method Field\Email          email($column, $label = '')
 * @method Field\Mobile         mobile($column, $label = '')
 * @method Field\Slider         slider($column, $label = '')
 * @method Field\Map            map($latitude, $longitude, $label = '')
 * @method Field\Editor         editor($column, $label = '')
 * @method Field\File           file($column, $label = '')
 * @method Field\Image          image($column, $label = '')
 * @method Field\Date           date($column, $label = '')
 * @method Field\Datetime       datetime($column, $label = '')
 * @method Field\Time           time($column, $label = '')
 * @method Field\DateRange      dateRange($start, $end, $label = '')
 * @method Field\DateTimeRange  dateTimeRange($start, $end, $label = '')
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
 * @method Field\Number         number($column, $label = '')
 * @method Field\Currency       currency($column, $label = '')
 * @method Field\Json           json($column, $label = '')
 * @method Field\HasMany        hasMany($relationName, $callback)
 * @method Field\SwitchField    switch ($column, $label = '')
 * @method Field\Display        display($column, $label = '')
 * @method Field\Rate           rate($column, $label = '')
 * @method Field\Divide         divide()
 * @method Field\Password       password($column, $label = '')
 * @method Field\Icon           icon($column, $label = '')
 */
class Form
{
    /**
     * Eloquent模型
     *
     * @var \Illuminate\Database\Eloquent\Model
     */
    protected $model;

    /**
     * 表单html构建器
     *
     * @var Builder
     */
    protected $builder;

    /**
     * 表单认证器
     *
     * @var \Illuminate\Validation\Validator
     */
    protected $validator;

    /**
     * 保存前的回调
     *
     * @var \Closure
     */
    protected $saving;

    /**
     * 保存后的回调
     *
     * @var \Closure
     */
    protected $saved;

    /**
     * 请求中的输入数据
     *
     * @var array
     */
    protected $inputs = [];

    /**
     * 当前模型更新时所需的数据
     *
     * @var array
     */
    protected $updates = [];

    /**
     * 模型关联相关的数据
     *
     * @var array
     */
    protected $relations = [];

    /**
     * 表单中使用了文件管理的文件上传相关数据
     *
     * @var array
     */
    protected $files = [];

    /**
     * 已注册的可用组件
     *
     * @var array
     */
    public static $availableFields = [];

    /**
     * 不需要保存字段
     *
     * @var array
     */
    protected $ignored = [];

    /**
     * 已收集过的各组件引入的js和css文件集合
     *
     * @var array
     */
    protected static $collectedAssets = [];

    /**
     * 一对多内联表单中的移除标记
     */
    const REMOVE_FLAG_NAME = '_remove_';

    /**
     * 有时,ajax请求成功后,不需要刷新页面,则不闪存消息
     */
    const REDIRECT_FLAG_NAME = '_stay_';

    /**
     * @var Form\Tab
     */
    protected $tab = null;

    /**
     * Form constructor.
     *
     * @param Model    $model
     * @param \Closure $callback
     */
    public function __construct($model, \Closure $callback)
    {
        $this->model = $model;

        $this->builder = new Builder($this);

        $callback($this);
    }

    /**
     * 返回该表单的模型
     *
     * @return Model
     */
    public function model()
    {
        return $this->model;
    }

    /**
     * 返回该表单的html构建器
     *
     * @return Builder
     */
    public function builder()
    {
        return $this->builder;
    }

    /**
     * 返回表单的html内容
     *
     * @return string
     * @throws \Throwable
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * 生成表单html字符串
     *
     * @return string
     * @throws \Throwable
     */
    public function render()
    {
        try {
            return $this->builder->render();
        } catch (\Exception $e) {
            return with(new Handle($e))->render();
        }
    }

    /**
     * 返回当前操作的资源的列表页
     *
     * @param int $slice 当前网址要退回的步数,按"/"分隔
     *
     * @return string
     */
    public function resource($slice = -2)
    {
        $route = \Route::current();

        $segments = explode('/', trim($route->uri(), '/'));

        if ($slice != 0) {
            $segments = array_slice($segments, 0, $slice);
        }

        return '/'.implode('/', $segments);
    }

    /**
     * 生成一个字段组件,并加入到表单和表单构造器中
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, $arguments)
    {
        if ($className = static::findFieldClass($method)) {
            $column = array_get($arguments, 0, '');

            $element = new $className($column, array_slice($arguments, 1));

            $this->pushField($element);

            return $element;
        }
    }

    /**
     * 获取字段组件的类名
     *
     * @param string $method
     *
     * @return bool|string
     */
    public static function findFieldClass($method)
    {
        $class = array_get(static::$availableFields, $method);

        if (class_exists($class)) {
            return $class;
        }

        return false;
    }

    /**
     * 向html构建器中添加一个字段
     *
     * @param Field $field
     *
     * @return $this
     */
    public function pushField(Field $field)
    {
        $field->setForm($this);

        $this->builder->fields()->push($field);

        return $this;
    }

    /**
     * 删除数据
     *
     * @param $id
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function destroy($id)
    {
        $ids = explode(',', $id);

        $ids = array_filter($ids, function ($id) {
            if (empty($id)) {
                return false;
            }

            $this->deleteFilesAndImages($id);

            return true;
        });

        if ($this->model->destroy($ids)) {
            flash_msg('删除成功');

            return response()->json([
                'status' => true,
            ]);
        } else {
            return response()->json([
                'status' => false,
            ]);
        }
    }

    /**
     * 生成一个编辑表单
     *
     * @param $id
     *
     * @return $this
     */
    public function edit($id)
    {
        $this->builder->setMode(Builder::MODE_EDIT);
        $this->builder->setResourceId($id);

        $this->setFieldValue($id);

        return $this;
    }

    /**
     * 生成一个查看表单
     *
     * @param $id
     *
     * @return $this
     */
    public function view($id)
    {
        $this->builder->setMode(Builder::MODE_VIEW);
        $this->builder->setResourceId($id);

        $this->setFieldValue($id);

        return $this;
    }

    /**
     * 获取该模型所有的关联方法名
     *
     * @return array
     */
    protected function getRelations()
    {
        $relations = $columns = [];

        foreach ($this->builder->fields() as $field) {
            $columns[] = $field->column();
        }

        foreach (array_flatten($columns) as $column) {
            $column = camel_case($column);
            if (str_contains($column, '.')) {
                list($relation) = explode('.', $column);

                if (
                    method_exists($this->model, $column) &&
                    $this->model->$column() instanceof Relation
                ) {
                    $relations[] = $relation;
                }
            } elseif (method_exists($this->model, $column)) {
                $relations[] = $column;
            }
        }

        return array_unique($relations);
    }

    /**
     * 更新关联数据
     *
     * @param $relationsData
     *
     * @throws \Exception
     */
    protected function updateRelation($relationsData)
    {
        foreach ($relationsData as $name => $values) {
            $relationName = camel_case($name);

            if (!method_exists($this->model, $relationName)) {
                continue;
            }

            $relation = $this->model->$relationName();

            // 文件管理的hasOne和hasMany关系特殊处理
            $field = $this->getFieldByColumn($name);
            if ($field instanceof Field\File) {
                $this->updateFile($relation, $name, $values);

                continue;
            }

            $hasDot = $relation instanceof \Illuminate\Database\Eloquent\Relations\HasOne;

            $prepared = $this->prepareUpdate([$name => $values], $hasDot);

            if (empty($prepared)) {
                continue;
            }

            switch (get_class($relation)) {
                case \Illuminate\Database\Eloquent\Relations\BelongsToMany::class:
                case \Illuminate\Database\Eloquent\Relations\MorphToMany::class:
                    /**
                     * @var \Illuminate\Database\Eloquent\Relations\BelongsToMany|\Illuminate\Database\Eloquent\Relations\MorphToMany $relation
                     */

                    if (isset($prepared[$name])) {
                        $relation->sync($prepared[$name]);
                    }
                    break;
                case \Illuminate\Database\Eloquent\Relations\HasOne::class:
                    /**
                     * @var \Illuminate\Database\Eloquent\Relations\HasOne $relation
                     */

                    $related = $this->model->$relationName;

                    if (is_null($related)) {
                        $related = $relation->getRelated();
                        $related->{$relation->getForeignKey()} = $this->model->{$this->model->getKeyName()};
                    }

                    $related->fill($prepared[$name]);
                    $related->save();
                    break;
                case \Illuminate\Database\Eloquent\Relations\HasMany::class:
                    /**
                     * @var \Illuminate\Database\Eloquent\Relations\HasMany $relation
                     */

                    foreach ($prepared[$name] as $related) {
                        $relation = $this->model()->$name();
                        $keyName = $relation->getRelated()->getKeyName();
                        $instance = $relation->findOrNew(array_get($related, $keyName));

                        if ($instance instanceof \App\Models\File) {

                        } else {
                            // 这里是一对多内联表单的相关移除操作,暂时不需要
                            if ($related[static::REMOVE_FLAG_NAME] == 1) {
                                $instance->delete();

                                continue;
                            }

                            array_forget($related, static::REMOVE_FLAG_NAME);

                            $instance->fill($related);
                            $instance->save();
                        }
                    }
                    break;
                default:
                    // null
            }
        }
    }

    /**
     * 处理使用了文件管理的的关联
     *
     * @param        $relation
     * @param string $name
     * @param string $values
     */
    protected function updateFile($relation, $name, $values)
    {
        $relation->update([
            'target_id'   => 0,
            'target_type' => '',
        ]);

        $targetTypeMethodName = camel_case($name).'FileType';

        // 更新files表的记录,关联到该字段
        FileManager::whereIn('id', (array) $values)
                   ->update([
                       'target_id'   => $this->model->id,
                       'target_type' => $this->model->$targetTypeMethodName(),
                   ]);
    }

    /**
     * 查看和编辑时,设置表单中各字段的值
     *
     * @param $id
     */
    protected function setFieldValue($id)
    {
        $relations = $this->getRelations();

        $this->model = $this->model->with($relations)->findOrFail($id);

        $data = $this->model->toArray();

        $this->builder->fields()->each(function (Field $field) use ($data) {
            $field->fill($data);
        });
    }

    /**
     * 添加一条记录
     *
     * @return Response
     */
    public function store()
    {
        $data = \Request::all();
        // 判断本次保存是不是树结构保存排序,如果是,则不需要进行表单验证
        $isTreeSaveOrder = Tree::isTree($this->model) && key_exists('_order', $data);

        if (($response = $this->handleFileUpload()) instanceof Response) {
            return $response;
        }

        // 如果本次请求不是数结构保存排序,且验证没通过,
        !$isTreeSaveOrder && !$this->validate($data);

        if (($response = $this->prepare($data)) instanceof Response) {
            return $response;
        }

        \DB::transaction(function () {
            $inserts = $this->prepareInsert($this->updates);

            $this->model->fill($inserts);
            $this->model->save();

            $this->updateRelation($this->relations);
        });

        if (($response = $this->complete($this->saved)) instanceof Response) {
            return $response;
        }

        // 如果是树结构保存排序,则返回这个
        if ($isTreeSaveOrder) {
            flash_msg('更新成功');

            return response()->json([
                'status' => true,
            ]);
        }

        if ($response = $this->ajaxResponse('保存成功', $this->resource(0))) {
            return $response;
        }

        return $this->redirectResponse('保存成功', $this->resource(0));
    }

    /**
     * 处理文件上传
     *
     * @return bool|Response
     */
    public function handleFileUpload()
    {
        if (empty($files = \Request::allFiles())) {
            return false;
        }

        $fieldNames = array_keys($files);
        $fieldName = array_shift($fieldNames);

        $field = $this->getFieldByColumn($fieldName);
        if (!$field) {
            return response()->json([$fieldName => '字段不存在，无法上传'])->setStatusCode(422);
        }

        $fileInfo = $field->uploadAndDeleteOriginal($fieldName);

        $res = ['status' => true];
        if ($fileInfo) {
            $res = array_merge($res, $fileInfo);
        }

        return response()->json($res);
    }

    /**
     * ajax请求的返回
     *
     * @param string $message
     * @param string $action 成功后需要跳转的页面地址
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function ajaxResponse($message, $action = '')
    {
        $request = \Request::capture();
        if (!$request->get(self::REDIRECT_FLAG_NAME)) {
            flash_msg($message);
        }

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json([
                'status' => true,
                'action' => \Request::get(Builder::PREVIOUS_URL_KEY) ?: $action,
            ]);
        }
    }

    /**
     * 表单保存后的回调
     *
     * @param \Closure|null $callback
     *
     * @return mixed
     */
    protected function complete(\Closure $callback = null)
    {
        if ($callback instanceof \Closure) {
            return $callback($this);
        }
    }

    /**
     * 更新
     *
     * @param $id
     *
     * @return Form|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed
     * @throws \Exception
     * @throws \Throwable
     */
    public function update($id)
    {
        $data = \Request::all();

        $data = $this->handleEditable($data);

        // $data = $this->handleFileDelete($data);

        // if ($this->handleOrderable($id, $data)) {
        //     return response()->json([
        //         'status' => true,
        //         'msg'    => '排序更新成功',
        //     ]);
        // }

        $this->model = $this->model->with($this->getRelations())->findOrFail($id);

        $this->setFieldOriginalValue();

        $this->validate($data);

        if (($response = $this->prepare($data)) instanceof Response) {
            return $response;
        }

        \DB::transaction(function () {
            $updates = $this->prepareUpdate($this->updates);

            $this->model->fill($updates);
            $this->model->save();

            $this->updateRelation($this->relations);
        });

        if (($response = $this->complete($this->saved)) instanceof Response) {
            return $response;
        }

        if ($response = $this->ajaxResponse('更新成功', $this->resource(-1))) {
            return $response;
        }

        return $this->redirectResponse('更新成功', $this->resource(-1));
    }

    /**
     * 非ajax更新或添加后跳转的地址
     *
     * @param        $message
     * @param string $action
     *
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    protected function redirectResponse($message, $action = '')
    {
        flash_msg($message);

        $url = \Request::get(Builder::PREVIOUS_URL_KEY) ?: $action;

        return redirect($url);
    }

    /**
     * 把表单验证错误消息中的数组类的验证消息合并
     *
     * 比如把所有的roles.0、roles.1合并为roles.*
     *
     * TODO 以后有其他需求在来修改
     *
     * @param array $errors
     *
     * @return array
     */
    protected function mergeArrayErrors(array $errors)
    {
        $p = '/\.(\d+)/';
        $mergedErrors = [];
        foreach ($errors as $key => $msg) {
            $starKey = preg_replace($p, '.*', $key);
            if (isset($mergedErrors[$starKey])) {
                $msgs = $mergedErrors[$starKey];
                $msgs = array_values($msgs) + array_values($msg);
            } else {
                $msgs = $msg;
            }
            $mergedErrors[$starKey] = $msgs;
        }

        return $mergedErrors;
    }

    /**
     * 处理数据表格中可排序的的更新
     *
     * @param       $id
     * @param array $input
     *
     * @return bool
     */
    protected function handleOrderable($id, array $input = [])
    {
        if (array_key_exists('_orderable_', $input)) {
            $model = $this->model->find($id);

            if ($model instanceof Sortable) {
                $input['_orderable_'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();

                return true;
            }
        }

        return false;
    }

    /**
     * 处理数据表格中的可编辑字段的保存
     *
     * @param array $input
     *
     * @return array
     */
    protected function handleEditable(array $input = [])
    {
        if (array_key_exists('_editable_', $input)) {
            $name = $input['name'];
            $value = $input['value'];

            array_forget($input, ['pk', 'name', 'value']);
            array_set($input, $name, $value);
        }

        return $input;
    }


    /**
     * 更新前,设定各字段更新前的值
     */
    protected function setFieldOriginalValue()
    {
        $values = $this->model->toArray();

        $this->builder->fields()->each(function (Field $field) use ($values) {
            $field->setOriginal($values);
        });
    }

    /**
     * 准备更新用的数据
     *
     * @param array $updates
     * @param bool  $hasDot
     *
     * @return array
     */
    protected function prepareUpdate(array $updates, $hasDot = false)
    {
        $prepared = [];

        foreach ($this->builder->fields() as $field) {
            $columns = $field->column();

            if (\Request::has('_editable_') && !array_has($updates, (array) $columns)) {
                continue;
            }

            if ($this->invalidColumn($columns, $hasDot)) {
                continue;
            }

            $value = $this->getDataByColumn($updates, $columns);

            if ($value === '' || is_null($value)) {
                $value = $field->getDefault();
            }

            if (is_null($value)) {
                continue;
            }

            // if ($value !== '' && $value !== '0' && empty($value)) {
            //     continue;
            // }

            if (method_exists($field, 'prepare')) {
                $value = $field->prepare($value);
            }

            if ($value != $field->original()) {
                if (is_array($columns)) {
                    foreach ($columns as $name => $column) {
                        array_set($prepared, $column, $value[$name]);
                    }
                } elseif (is_string($columns)) {
                    array_set($prepared, $columns, $value);
                }
            }
        }

        return $prepared;
    }

    /**
     * TODO 暂时不知道这个是干嘛的,以及这个hasDot一系列的判断
     *
     * @param      $columns
     * @param bool $hasDot
     *
     * @return bool
     */
    public function invalidColumn($columns, $hasDot = false)
    {
        foreach ((array) $columns as $column) {
            if (
                (!$hasDot && Str::contains($column, '.')) ||
                ($hasDot && !Str::contains($column, '.'))
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * 插入输入数据的清理:调用saving回调,去除null字段,分离关联字段
     *
     * @param array $data
     *
     * @return mixed
     */
    protected function prepare($data = [])
    {
        $this->inputs = $this->removeIgnoredFields($data);

        if (($response = $this->callSaving()) instanceof Response) {
            return $response;
        }

        $this->relations = $this->getRelationInputs($this->inputs);

        // 从输入数据中去掉关联字段
        $updates = array_except($this->inputs, array_keys($this->relations));

        $this->updates = array_filter($updates, function ($val) {
            return !is_null($val);
        });
    }

    /**
     * 保存时,获取该模型的关联模型的查询构造器
     *
     * @param array $inputs
     *
     * @return array
     */
    protected function getRelationInputs($inputs = [])
    {
        $relations = [];

        foreach ($this->builder()->fields() as $field) {
            /**
             * @var Field $field
             */
            if ($field instanceof Field\Display) {
                continue;
            }

            $column = $field->column();
            $relationName = camel_case($column);
            if (method_exists($this->model, $relationName)) {
                $relation = call_user_func([$this->model, $relationName]);
                if (isset($inputs[$column])) {
                    $value = $inputs[$column];
                } elseif (array_key_exists('_editable_', $inputs)) {
                    // 如果更新字段中,没有该关联,并且是表格中的快捷编辑,则跳过
                    continue;
                } else {
                    // 否则,设置关联的默认值,比如一对多关系,则应该是个空数组,
                    $value = $field->getDefault();
                }


                if ($relation instanceof Relation) {
                    $relations[$column] = $value;
                }
            }
        }

        return $relations;
    }

    /**
     * 调用保存前的回调
     *
     * @return mixed
     */
    protected function callSaving()
    {
        if ($this->saving instanceof \Closure) {
            return call_user_func($this->saving, $this);
        }
    }

    /**
     * 移除不需要保存的字段
     *
     * @param $input
     *
     * @return mixed
     */
    protected function removeIgnoredFields($input)
    {
        array_forget($input, $this->ignored);

        return $input;
    }

    /**
     * 插入数据前,准备好插入的数据
     *
     * @param $inserts
     *
     * @return array
     */
    protected function prepareInsert($inserts)
    {
        if ($this->isHasOneRelation($inserts)) {
            $inserts = array_dot($inserts);
        }

        foreach ($inserts as $column => $value) {
            if (is_null($field = $this->getFieldByColumn($column))) {
                unset($inserts[$column]);
                continue;
            }

            // 调用该字段组件的prepare方法,修改数据,如果有的话
            if (method_exists($field, 'prepare')) {
                $inserts[$column] = $field->prepare($value);
            }
        }

        $prepared = [];
        foreach ($inserts as $key => $value) {
            array_set($prepared, $key, $value);
        }

        return $prepared;
    }

    /**
     * 判断输入数据是不是一个HasOne关联
     *
     * @param $inserts
     *
     * @return bool
     */
    protected function isHasOneRelation($inserts)
    {
        $first = current($inserts);

        if (!is_array($first)) {
            return false;
        }

        if (is_array(current($first))) {
            return false;
        }

        return Arr::isAssoc($first);
    }

    /**
     * 获取特定字段的值
     *
     * @param $data
     * @param $columns
     *
     * @return array|mixed
     */
    protected function getDataByColumn($data, $columns)
    {
        if (is_string($columns)) {
            return array_get($data, $columns);
        }

        if (is_array($columns)) {
            $value = [];
            foreach ($columns as $name => $column) {
                if (!array_has($data, $column)) {
                    continue;
                }
                $value[$name] = array_get($data, $column);
            }

            return $value;
        }
    }

    /**
     * 通过字段名获取相应的表单组件Field实例
     *
     * @param $column
     *
     * @return mixed
     */
    protected function getFieldByColumn($column)
    {
        return $this->builder->fields()->first(
            function (Field $field) use ($column) {
                if (is_array($field->column())) {
                    return in_array($column, $field->column());
                }

                return $field->column() == $column;
            }
        );
    }

    /**
     * 表单验证
     *
     * @param $data
     *
     * @return bool
     */
    protected function validate($data)
    {
        if ($this->noNeedValidate()) {
            return true;
        }

        $this->validator = \Validator::make(
            $data,
            $this->updateRules($this->model->validateRules(), $data),
            $this->model->validateMessages(),
            $this->model->validateAttributes()
        );

        if ($this->model->validateSometimes() instanceof \Closure) {
            $this->model->validateSometimes($this->validator);
        }

        if ($this->validator->fails() && !\Request::wantsJson() && !\Request::ajax()) {
            flash_msg('请填写正确的数据', 'warning');
        }

        $this->validator->validate();
    }

    private function noNeedValidate()
    {
        $allTraits = (new \ReflectionClass($this->model))->getTraits();

        return !isset($allTraits[CanValidate::class]);
    }

    /**
     * 更新时,如果是表格的可编辑字段更新,则只需要验证该字段,其他字段忽略
     *
     * @param $rules
     * @param $data
     *
     * @return array
     */
    protected function updateRules($rules, $data)
    {
        if (\Request::isMethod('put') && \Request::has('_editable_')) {
            $rules = array_only($rules, array_keys($data));
        }

        return $rules;
    }

    /**
     * 收集各组件所引入的js和css文件
     *
     * @return array
     */
    public static function collectFieldAssets()
    {
        if (!empty(static::$collectedAssets)) {
            return static::$collectedAssets;
        }

        $css = collect();
        $js = collect();

        foreach (static::$availableFields as $field) {
            if (!method_exists($field, 'getAssets')) {
                continue;
            }

            $assets = call_user_func([$field, 'getAssets']);

            $css->push(array_get($assets, 'css'));
            $js->push(array_get($assets, 'js'));
        }

        return static::$collectedAssets = [
            'css' => $css->flatten()->unique()->filter()->toArray(),
            'js'  => $js->flatten()->unique()->filter()->toArray(),
        ];
    }

    /**
     * 设置保存前的回调
     *
     * @param \Closure $callback
     */
    public function saving(\Closure $callback)
    {
        $this->saving = $callback;
    }

    /**
     * 设置保存后的回调
     *
     * @param \Closure $callback
     */
    public function saved(\Closure $callback)
    {
        $this->saved = $callback;
    }

    /**
     * 设置需要忽略保存的字段
     *
     * @param $fields
     *
     * @return $this
     */
    public function ignore($fields)
    {
        $this->ignored = array_merge($this->ignored, (array) $fields);

        return $this;
    }

    /**
     * 从请求的数据中获取一个值
     *
     * @param string $name
     *
     * @return array|mixed
     */
    public function __get($name)
    {
        return $this->input($name);
    }

    /**
     * 从请求的数据中获取或者设置一个值
     *
     * @param      $key
     * @param null $value
     *
     * @return array|mixed
     */
    public function input($key, $value = null)
    {
        if (is_null($value)) {
            return array_get($this->inputs, $key);
        }

        return array_set($this->inputs, $key, $value);
    }

    /**
     * 修改请求数据中的数据
     *
     * @param string $name
     * @param        $value
     */
    public function __set($name, $value)
    {
        $this->input($name, $value);
    }

    /**
     * 删除文件和图片
     *
     * @param $id
     */
    protected function deleteFilesAndImages($id)
    {
        // TODO 删除数据后,删除其关联的文件和图片
    }

    /**
     * 禁用表单的重置按钮
     *
     * @return $this
     */
    public function disableReset()
    {
        $this->builder()->options(['enableReset' => false]);

        return $this;
    }

    /**
     * 禁用表单的提交
     *
     * @return $this
     */
    public function disableSubmit()
    {
        $this->builder()->options(['enableSubmit' => false]);

        return $this;
    }

    /**
     * 添加自定义表单组件
     *
     * @param string $abstract
     * @param string $class
     */
    public static function extend($abstract, $class)
    {
        static::$availableFields[$abstract] = $class;
    }

    /**
     * 移除一个已注册的可用表单组件
     *
     * @param array|string $abstract
     */
    public static function forget($abstract)
    {
        array_forget(static::$availableFields, $abstract);
    }

    public static function registerBuiltinFields()
    {
        $map = [
            // 'button'         => \App\Admin\Form\Field\Button::class,
            'checkbox' => \App\Admin\Form\Field\Checkbox::class,
            // 'color'          => \App\Admin\Form\Field\Color::class,
            // 'currency'       => \App\Admin\Form\Field\Currency::class,
            // 'date'           => \App\Admin\Form\Field\Date::class,
            // 'dateRange'      => \App\Admin\Form\Field\DateRange::class,
            // 'datetime'       => \App\Admin\Form\Field\Datetime::class,
            // 'dateTimeRange'  => \App\Admin\Form\Field\DatetimeRange::class,
            // 'datetimeRange'  => \App\Admin\Form\Field\DatetimeRange::class,
            // 'decimal'        => \App\Admin\Form\Field\Decimal::class,
            'display'  => \App\Admin\Form\Field\Display::class,
            // 'divider'        => \App\Admin\Form\Field\Divide::class,
            // 'divide'         => \App\Admin\Form\Field\Divide::class,
            // 'embeds'         => \App\Admin\Form\Field\Embeds::class,
            // 'editor'         => \App\Admin\Form\Field\Editor::class,
            // 'email'          => \App\Admin\Form\Field\Email::class,
            'file'     => \App\Admin\Form\Field\File::class,
            // 'hasMany'        => \App\Admin\Form\Field\HasMany::class,
            'hidden'   => \App\Admin\Form\Field\Hidden::class,
            // 'id'             => \App\Admin\Form\Field\Id::class,
            // 'image'          => \App\Admin\Form\Field\Image::class,
            // 'ip'             => \App\Admin\Form\Field\Ip::class,
            // 'map'            => \App\Admin\Form\Field\Map::class,
            // 'mobile'         => \App\Admin\Form\Field\Mobile::class,
            // 'month'          => \App\Admin\Form\Field\Month::class,
            // 'multipleSelect' => \App\Admin\Form\Field\MultipleSelect::class,
            // 'number'         => \App\Admin\Form\Field\Number::class,
            'password' => \App\Admin\Form\Field\Password::class,
            // 'radio'          => \App\Admin\Form\Field\Radio::class,
            // 'rate'           => \App\Admin\Form\Field\Rate::class,
            'select'   => \App\Admin\Form\Field\Select::class,
            // 'slider'         => \App\Admin\Form\Field\Slider::class,
            'switch'   => \App\Admin\Form\Field\SwitchField::class,
            'text'     => \App\Admin\Form\Field\Text::class,
            'textarea' => \App\Admin\Form\Field\Textarea::class,
            // 'time'           => \App\Admin\Form\Field\Time::class,
            // 'timeRange'      => \App\Admin\Form\Field\TimeRange::class,
            // 'year'           => \App\Admin\Form\Field\Year::class,
            // 'html'           => \App\Admin\Form\Field\Html::class,
            // 'tags'           => \App\Admin\Form\Field\Tags::class,
            'icon'     => \App\Admin\Form\Field\Icon::class,
            // 'multipleFile'   => \App\Admin\Form\Field\MultipleFile::class,
            // 'multipleImage'  => \App\Admin\Form\Field\MultipleImage::class,
            // 'captcha'        => \App\Admin\Form\Field\Captcha::class,
        ];

        foreach ($map as $abstract => $class) {
            static::$availableFields[$abstract] = $class;
        }
    }

    /**
     * 设置表单的action属性
     *
     * @param $action
     *
     * @return $this
     */
    public function setAction($action)
    {
        $this->builder()->setAction($action);

        return $this;
    }

    /**
     * 设置表单所用的blade模板
     *
     * @param $view
     *
     * @return $this
     */
    public function setView($view)
    {
        $this->builder()->setView($view);

        return $this;
    }

    /**
     * 设置表单中每个组件的标签和输入区的宽度
     *
     * @param int $fieldWidth
     * @param int $labelWidth
     *
     * @return $this
     */
    public function setWidth($fieldWidth = 380, $labelWidth = 150)
    {
        list($fieldWidth, $labelWidth) = Field::checkWidth($fieldWidth, $labelWidth);

        $this->builder()->fields()->each(function (Field $field) use ($fieldWidth, $labelWidth) {
            $field->setWidth($fieldWidth, $labelWidth);
        });

        $this->builder()->setWidth($fieldWidth, $labelWidth);

        return $this;
    }

    /**
     * 表单顶部功能按钮设置
     *
     * @param \Closure $callback
     */
    public function tools(\Closure $callback)
    {
        $callback = $callback->bindTo($this);

        call_user_func($callback, $this->builder->getTools());
    }

    /**
     * 返回Tab实例
     *
     * @return Tab
     */
    public function getTab()
    {
        if (is_null($this->tab)) {
            $this->tab = new Tab($this);
        }

        return $this->tab;
    }

    public function tab($title, \Closure $content, $active = false)
    {
        $this->getTab()->append($title, $content, $active);

        return $this;
    }
}